1.18 Code Optimization in C Programming
Module 1.18 • Algorithmic Efficiency, Compiler Flags, Loop Invariants & Memory Alignment
1.18.1 Introduction
Code optimization is the process of improving a program so that it executes faster, consumes less memory, and utilizes system resources more efficiently without changing the program's output.
Optimization is not simply about reducing the number of lines of code. Sometimes a longer program can execute faster than a shorter one. Effective optimization focuses on improving performance, memory usage, maintainability, and scalability.
Optimization is especially important in:
- Embedded Systems
- Real-Time Applications
- Operating Systems
- Device Drivers
- High Performance Computing
- Game Development
1.18.2 Why Optimization Matters
A poorly optimized program may:
- Consume excessive CPU time
- Waste memory resources
- Increase power consumption
- Slow down other applications
- Require expensive hardware upgrades
An optimized program:
- Executes faster
- Uses fewer resources
- Improves system responsiveness
- Reduces operational costs
1.18.3 Types of Optimization
Code optimization can be divided into:
- Algorithm Optimization
- Compiler Optimization
- Loop Optimization
- Mathematical Optimization
- Memory Optimization
- Function Optimization
- Data Structure Optimization
- Parallel Processing Optimization
1.18.4 Algorithm Optimization
Choosing the correct algorithm provides the largest performance improvement.
Example
Searching an element in:
10 Elements
Both methods work similarly.
Searching in: 1,000,000 Elements
The difference becomes significant.
| Algorithm | Complexity |
|---|---|
| Linear Search | O(n) |
| Binary Search | O(log n) |
Example: Linear Search
#include<stdio.h>
int main()
{
int arr[6]={12,24,36,48,60,72};
int key=60;
int found=0;
for(int i=0;i<6;i++)
{
if(arr[i]==key)
{
found=1;
break;
}
}
printf("%s",found?"Found":"Not Found");
return 0;
}
Example: Binary Search
#include<stdio.h>
int main()
{
int arr[]={10,20,30,40,50,60,70};
int low=0;
int high=6;
int key=60;
while(low<=high)
{
int mid=(low+high)/2;
if(arr[mid]==key)
{
printf("Found");
break;
}
else if(arr[mid]<key)
low=mid+1;
else
high=mid-1;
}
return 0;
}
Binary Search performs much better on large sorted datasets.
1.18.5 Compiler Optimization
Modern compilers automatically optimize code.
Common GCC optimization levels:
-O0 No Optimization
-O1 Basic Optimization
-O2 Recommended Optimization
-O3 Aggressive Optimization
-Os Optimize for Size
Example:
gcc -O2 program.c -o program
Compiler optimizations include:
- Dead code removal
- Constant folding
- Function inlining
- Register allocation
- Loop optimization
1.18.6 Loop Optimization
Loops consume significant CPU time. Optimizing loops can greatly improve performance.
Example: Inefficient Loop
for(int i=0;i<100;i++)
{
values[i]=length*width*i;
}
The expression: length*width is calculated 100 times.
Optimized Version
int area = length * width;
for(int i=0;i<100;i++)
{
values[i]=area*i;
}
Now the multiplication is performed only once.
1.18.7 Loop Unrolling
Loop unrolling reduces loop overhead.
Normal Loop
for(int i=0;i<8;i++)
{
data[i]*=2;
}
Unrolled Loop
for(int i=0;i<8;i+=4)
{
data[i]*=2;
data[i+1]*=2;
data[i+2]*=2;
data[i+3]*=2;
}
Advantages:
- Fewer loop checks
- Fewer increment operations
- Better CPU utilization
1.18.8 Avoid Calculations Inside Loops
Bad Practice:
for(int i=0;i<500;i++)
{
result[i]=x*(40-y/2)*i;
}
Optimized Version:
int factor=x*(40-y/2);
for(int i=0;i<500;i++)
{
result[i]=factor*i;
}
This reduces unnecessary computations.
1.18.9 Mathematical Optimization
Some mathematical operations are slower than others. Speed order:
- Addition
- Subtraction
- Multiplication
- Division
- Modulus
Division is usually slower.
Example
Instead of:
result=a/b/c;
Use:
result=a/(b*c);
This reduces one division operation.
1.18.10 Bitwise Optimization
Bit operations are extremely fast.
Multiplication by Power of 2
Instead of:
value=value*16;
Use:
value=value<<4;
Because: 16 = 2⁴
Division by Power of 2
Instead of:
value=value/8;
Use:
value=value>>3;
Because: 8 = 2³
1.18.11 Expression Simplification
Reduce unnecessary operations.
Original
price*tax + tax*5
Simplified
(price+5)*tax
One multiplication operation is eliminated.
1.18.12 Function Optimization
Function calls create overhead. Each function call requires:
- Stack setup
- Parameter passing
- Return address storage
Example
Small functions can be declared as:
inline int square(int x)
{
return x*x;
}
This reduces call overhead.
1.18.13 Use Function Prototypes
Always declare function prototypes.
int calculateTotal(int,int);
Benefits:
- Better compiler optimization
- Type checking
- Faster compilation
1.18.14 Efficient Parameter Passing
Bad Practice:
void display(struct Employee emp)
Entire structure gets copied.
Better Practice:
void display(struct Employee *emp)
Only an address is passed.
Benefits:
- Less memory usage
- Faster execution
1.18.15 Register Variables
Frequently used variables can be suggested for register storage.
register int counter;
Example:
for(register int i=0;i<10000;i++)
{
sum+=i;
}
Registers are faster than RAM. Note: Modern compilers automatically perform register allocation.
1.18.16 Memory Optimization
Memory operations are expensive.
Bad Practice
for(int i=0;i<100;i++)
{
int *ptr=(int*)malloc(sizeof(int));
free(ptr);
}
Memory is repeatedly allocated and freed.
Better Practice
int *ptr=(int*)malloc(100*sizeof(int));
for(int i=0;i<100;i++)
{
ptr[i]=i;
}
free(ptr);
Allocate once and reuse memory.
1.18.17 Arrays vs Dynamic Allocation
When size is known, prefer:
int marks[100];
Instead of:
int *marks= malloc(100*sizeof(int));
Advantages:
- Faster access
- No allocation overhead
- Simpler management
1.18.18 Pre-Increment vs Post-Increment
Prefer: ++i Instead of: i++ when the previous value is not needed.
Example:
for(int i=0;i<100;i++)
can be written as:
for(int i=0;i<100;++i)
This may generate slightly more efficient machine code.
1.18.19 Switch Statement Optimization
Good Practice:
switch(choice)
{
case 1:
case 2:
case 3:
printf("Valid");
break;
default:
printf("Invalid");
}
Grouping common cases reduces code duplication.
1.18.20 Pointer Optimization
Repeated pointer dereferencing can reduce performance.
Less Efficient:
for(int i=0;i<n;i++)
{
*ptr = *ptr + 1;
}
Better:
int temp=*ptr;
for(int i=0;i<n;i++)
{
temp++;
}
*ptr=temp;
1.18.21 String Comparison Optimization
String comparisons are relatively slow. Instead of immediately calling: strcmp(str1,str2);, Check: strlen(str1)==strlen(str2) first. If lengths differ, comparison can be skipped.
1.18.22 Variable Declaration Order
Proper ordering can reduce padding and alignment overhead.
Less Efficient:
char a;
double b;
int c;
Better:
double b;
int c;
char a;
This helps memory alignment.
1.18.23 Short-Circuit Evaluation
Logical operators stop evaluating when the result is already known.
OR Operator
A || B
If A is true, B is not evaluated. Place the condition most likely to be true first.
AND Operator
A && B
If A is false, B is skipped. Place the condition most likely to be false first.
1.18.24 Parallel Processing
Modern processors contain multiple cores. Workloads can be divided among multiple threads.
Example Concept:
#pragma omp parallel for
Applications:
- Image Processing
- Scientific Computing
- Artificial Intelligence
- Simulations
1.18.25 Profiling Before Optimization
Never optimize blindly. First measure performance.
Tools:
gprof
perf
Valgrind
Visual Studio Profiler
Optimization should focus on bottlenecks.
1.18.26 Common Optimization Mistakes
Premature Optimization
Optimizing code before identifying performance problems.
Over-Optimization
Making code difficult to understand.
Ignoring Readability
Readable code is often more valuable than tiny performance gains.
Memory Leaks
Optimized programs must still release resources properly.
1.18.27 Best Practices
- Choose efficient algorithms.
- Avoid repeated calculations.
- Minimize dynamic allocations.
- Use compiler optimization flags.
- Keep loops simple.
- Use appropriate data structures.
- Pass large objects using pointers.
- Profile before optimizing.
- Balance speed and readability.
Summary
- Optimization improves speed, memory usage, and resource efficiency.
- Algorithm selection provides the greatest performance gains.
- Compiler optimization flags can significantly improve execution speed.
- Loop unrolling and calculation reduction improve performance.
- Bitwise operations are faster for powers of two.
- Memory allocations should be minimized.
- Efficient parameter passing reduces overhead.
- Profiling tools should be used before optimization.
- Optimization should improve performance without sacrificing correctness.
Click your choice for each question to view feedback immediately. Complete all questions to evaluate your metric score.